﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.CodeDom;

using Alicanto.Compilation;

namespace Alicanto.Execution
{
    public class ExecutionContext : IDynamicMetaObjectProvider
    {
        /// <summary>
        /// The scope that is compiled against this context
        /// </summary>
        internal Scope Scope { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        internal IDictionary<string, ExecutionContextMember> _members { get; private set; }

        public ExecutionDomain Domain { get; private set; }
        public ExecutionContext Outer { get; private set; }
        public IRuntimeVariables RuntimeVariables { get; set; }

        internal ExecutionContext(Scope scope, ExecutionDomain domain, ExecutionContext outer)
        {
            this.Domain = domain;
            this.Scope = scope;
            this.Outer = outer;
            _members = new Dictionary<string, ExecutionContextMember>();
        }

        #region IDynamicMetaObjectProvider implementation
        public DynamicMetaObject GetMetaObject(Expression parameter)
        {
            return new ExecutionMeta(parameter, this);
        }
        #endregion

        #region Class behaviour

        public ExecutionContext construct()
        {
            ExecutionContextInstance instance = new ExecutionContextInstance(this);
            return instance;
        }

        #endregion

        internal ExecutionContextMember this[string reference]
        {
            get
            {
                ExecutionContext context = this;
                while (context != null)
                {
                    if (context._members.ContainsKey(reference))
                    {
                        return context._members[reference];
                    }

                    context = context.Outer;
                }

                return null;
            }
            set
            {
                ExecutionContext context = this;
                while (context != null)
                {
                    if (context._members.ContainsKey(reference))
                    {
                        context._members[reference] = value;
                        return;
                    }

                    context = context.Outer;
                }

                _members[reference] = value;
            }
        }

        internal bool HasMember(string reference)
        {
            if (this[reference] != null)
            {
                return true;
            }
            else
            {
                if (reference == "construct")
                    return true;
            }

            return false;
        }

        internal Type GetReturnTypeFor(string reference)
        {
            if (this[reference] != null)
            {
                object member = this[reference];
                if (member is LambdaExpression)
                    return (member as LambdaExpression).ReturnType;
                else if (member is ExecutionContext)
                    return member.GetType();
            }

            return typeof(object);
        }
    }
}
